
package jason.bb;

import jason.RevisionFailedException;
import jason.asSemantics.*;
import jason.asSyntax.*;
import jason.bb.DefaultBeliefBase.*;
import static jason.util.Util.*;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

public class DeltaBeliefBase implements Iterable<Literal> {
	
	private static Logger logger = Logger.getLogger(DeltaBeliefBase.class.getSimpleName());
	
	private Agent agent;
	private Map<PredicateIndicator, BelEntry> belsMap = new ConcurrentHashMap<PredicateIndicator, BelEntry>();
	private HashMap<Literal, Literal> ignore = new HashMap<Literal, Literal>();
	private LinkedList<Triple<Literal, Literal, Intention>> entries = new LinkedList<Triple<Literal, Literal, Intention>>();
	
	public DeltaBeliefBase(Agent agent) {
		this.agent = agent;
	}
	
	public void clear() {
		belsMap.clear();
		ignore.clear();
		entries.clear();
	}
	
	public void newEntry(Literal beliefToAdd, Literal beliefToDel, Intention intention) {
		entries.add(new Triple<Literal, Literal, Intention>(beliefToAdd == null ? null : beliefToAdd.copy(), beliefToDel == null ? null : beliefToDel.copy(), intention == null ? null : intention.clone()));
		
		if (beliefToAdd != null) {
			if (!isIgnore(beliefToAdd)) {
				Literal bl = agent.getBB().contains(beliefToAdd);
				if (bl != null) {
					Literal l = bl.copy();
					ignore.put(bl, l);
					add(l);
				}
			}
			add(beliefToAdd);
		}
		if (beliefToDel != null) {
			Unifier u = null;
			try {
				u = intention.peek().getUnif(); // get from current intention
			} catch (Exception e) {
				u = new Unifier();
			}
			
			try {
				if (agent.believes(beliefToDel, u)) {
					beliefToDel.apply(u);
					
					if (!isIgnore(beliefToAdd)) {
						Literal bl = agent.getBB().contains(beliefToDel);
						if (bl != null && beliefToDel.hasSubsetAnnot(bl)) {
							Literal l = bl.copy();
							ignore.put(bl, l);
							add(l);
						}
					}
					remove(beliefToDel);
				}
			} catch (Exception e) {
				logger.log(Level.SEVERE, "** Error in method newEntry(" + beliefToAdd + "," + beliefToDel + "," + intention + ").", e);
			}
		}
	}
	
	/**
	 * @return the entries
	 */
	public final List<Triple<Literal, Literal, Intention>> getEntries() {
		return Collections.unmodifiableList(entries);
	}
	
	public boolean add(Literal l) {
		return add(l, false);
	}
	
	private boolean add(Literal l, boolean addInEnd) {
		if (!l.canBeAddedInBB()) {
			logger.log(Level.SEVERE, "Error: '" + l + "' can not be added in the belief base.");
			return false;
		}
		
		Literal bl = contains(l);
		if (bl != null && !bl.isRule()) {
			// add only annots
			if (bl.importAnnots(l)) {
				return true;
			}
		} else {
			BelEntry entry = belsMap.get(l.getPredicateIndicator());
			if (entry == null) {
				entry = new BelEntry();
				belsMap.put(l.getPredicateIndicator(), entry);
			}
			entry.add(l, addInEnd);
			return true;
		}
		return false;
	}
	
	public boolean remove(Literal l) {
		Literal bl = contains(l);
		if (bl != null) {
			if (l.hasSubsetAnnot(bl)) { // e.g. removing b[a] or b[a,d] from BB b[a,b,c]
				// second case fails
				boolean result = bl.delAnnots(l.getAnnots()); // note that l annots can be empty, in this case, nothing is deleted!
				return removeFromEntry(bl) || result;
			}
		} else {
			if (logger.isLoggable(Level.FINE)) logger.fine("Does not contain " + l + " in " + belsMap);
		}
		return false;
	}
	
	private boolean removeFromEntry(Literal l) {
		if (l.hasSource()) {
			return false;
		} else {
			PredicateIndicator key = l.getPredicateIndicator();
			BelEntry entry = belsMap.get(key);
			entry.remove(l);
			if (entry.isEmpty()) {
				belsMap.remove(key);
			}
			if (isIgnore(l)) ignore.put(l, null);
			return true;
		}
	}
	
	public Literal contains(Literal l) {
		BelEntry entry = belsMap.get(l.getPredicateIndicator());
		if (entry == null) {
			return null;
		} else {
			return entry.contains(l);
		}
	}
	
	public Iterator<Literal> getCandidateBeliefs(Literal l, Unifier u) {
		if (l.isVar()) {
			// all bels are relevant
			return iterator();
		} else {
			BelEntry entry = belsMap.get(l.getPredicateIndicator());
			if (entry != null) {
				return entry.getList().iterator();
			}
			
			return null;
		}
	}
	
	public Iterator<Literal> iterator() {
		final Iterator<BelEntry> ibe = belsMap.values().iterator();
		if (ibe.hasNext()) {
			return new Iterator<Literal>() {
				
				Iterator<Literal> il = ibe.next().getList().iterator();
				
				public boolean hasNext() {
					return il.hasNext();
				}
				
				public Literal next() {
					Literal l = il.next();
					if (!il.hasNext() && ibe.hasNext()) {
						il = ibe.next().getList().iterator();
					}
					return l;
				}
				
				public void remove() {
					il.remove();
				}
			};
		} else {
			return new ArrayList<Literal>(0).iterator();
		}
	}
	
	public DeltaBeliefBase clone() {
		DeltaBeliefBase dbb = new DeltaBeliefBase(agent);
		for(Triple<Literal, Literal, Intention> e : dbb.entries) {
			dbb.newEntry(e.fst, e.snd, e.trd);
		}
		return dbb;
	}
	
	/**
	 * Returns true if DBB contains the literal <i>bel</i> (using unification to test).
	 */
	public boolean believes(LogicalFormula bel, Unifier un) {
		try {
			Iterator<Unifier> iun = bel.logicalConsequence(agent, un, 1);
			if (iun != null && iun.hasNext()) {
				return true;
			}
		} catch (Exception e) {
			logger.log(Level.SEVERE, "** Error in method believes(" + bel + "," + un + ").", e);
		}
		return false;
	}
	
	/**
	 * Returns true if DBB adds/dels contains the literal <i>bel</i> (using unification to test), and deletes the entry
	 * found.
	 */
	public boolean deleteIfbelieves(LogicalFormula bel, Unifier un) {
		try {
			Iterator<Unifier> iun = bel.logicalConsequence(agent, un, 1);
			if (iun != null && iun.hasNext()) {
				iun.remove();
				return true;
			}
		} catch (Exception e) {
			logger.log(Level.SEVERE, "** Error in method deleteIfbelieves(" + bel + "," + un + ").", e);
		}
		return false;
	}
	
	public final boolean isIgnore(Literal lit) {
		return ignore.containsKey(lit);
	}
	
	public final Literal getIgnore(Literal lit) {
		return ignore.get(lit);
	}
}
